package com.techshinogongjian.fp.comm;

import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbEndpoint;
import android.os.Environment;

import com.fingerUsb.FingerResult;
import com.techshinogongjian.fingerprint.FPConfig;
import com.techshinogongjian.fingerprint.TCFP;
import com.techshinogongjian.fp.util.FpCoder;
import com.techshinogongjian.fp.util.FpConfig;
import com.techshinogongjian.fp.util.FpConst;
import com.techshinogongjian.fp.util.FpImage;
import com.techshinogongjian.fp.util.Logs;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * 简单协议
 *
 * @author wangzhi
 */
public class SimpleComm extends IComm {

    private static final String TAG = SimpleComm.class.getSimpleName();

    private static final int MAX_USBFS_BUFFER_SIZE = 16384;

    private static final int DIRECTION_IN = 0x80; // 返回数据
    private static final int DIRECTION_OUT = 0x00;// 无返回

    private static final int IMAGE_SMALL = 0;
    private static final int IMAGE_BIG = 1;

    private UsbDeviceConnection mConn;
    private UsbEndpoint mInEndpoint;
    private UsbEndpoint mOutEndpoint;

    private int mImgSize;
    private int mWidth;
    private int mHeight;
    private int mDPI;

    // 命令行发送缓冲区
    private byte[] bSendBuf = null;
    private int nSendDatLen = 0;
    // 接收缓冲区
    private byte[] bRecvBuf = null;
    private int nRecvDatLen = 0;
    private byte[] bImg;
    private byte[] bImgHead;

    private int nTimeoutDoFeature = 18000; // 取验证指纹的超时限定
    private int nTimeoutDoComm = 1000; // 单个指令通信超时限定
    private int nTimeoutDoTemplt = 32000; // 去注册模版的超时限定
    private int mWriteTimeout = 1000;

    private boolean isInitOk = false;
    private MsgCallback mCallback;

    private int mPid;

    public SimpleComm(UsbDeviceConnection usbDeviceConnection,
                      UsbEndpoint inEndpoint, UsbEndpoint outEndpoint) {
        mConn = usbDeviceConnection;
        mInEndpoint = inEndpoint;
        mOutEndpoint = outEndpoint;

        bSendBuf = new byte[1024];
        bRecvBuf = new byte[1024];
    }

    public void setPid(int pid) {
        mPid = pid;
    }

    public void setInitOk(boolean isInitOk) {
        this.isInitOk = isInitOk;
    }

    public int getRecvDatLen() {
        return nRecvDatLen;
    }

    public byte[] getbRecvBuf() {
        return bRecvBuf;
    }

    @Override
    public int read(byte[] pbReadOutBuf, int nStartPos, int nWantReadLen) {
        int nRet;
        synchronized (this) {
            nRet = mConn.bulkTransfer(mInEndpoint, pbReadOutBuf, nWantReadLen,
                    nTimeoutDoComm);
        }

        return nRet;
    }

    @Override
    public int write(byte[] data) {
        int ret;
        synchronized (this) {
            ret = mConn.bulkTransfer(mOutEndpoint, data, data.length,
                    mWriteTimeout);
        }
        return ret;
    }

    @Override
    public boolean isCancel() {
        return nCancelFlag == 1;
    }

    public void setCancel() {
        nCancelFlag = 1;
    }

    /*
     * 执行 Simple协议命令
     */
    public int doCommand(int nCmdId) {
        return doCommand(nCmdId, nTimeoutDoComm);
    }

    /*
     * 执行 Simple协议命令
     */
    public int doCommand(int nCmdId, int timeout) {
        return doCommand(nCmdId, 0, timeout);
    }

    /*
     * 执行 Simple协议命令
     */
    public int doCommand(int nCmdId, int nVal, int timeout) {
        int nRet = 0;
        byte[] bytes = getSendBytes();
        Logs.i(TAG, "doCommand Start ");
        // 尝试清空缓冲
        // readClear();
        bytes[12] = (byte) DIRECTION_OUT;
        bytes[16] = (byte) nCmdId;
        bytes[17] = (byte) nVal;
        Logs.i(TAG, "doCommand End nRet = " + nRet);
        nRet = write(bytes);

        nRet = read(bRecvBuf, 0, 64);
        return nRet;
    }

    public int doCommandIn(int nCmdId) {
        int nRet = 0;
        byte[] bytes = getSendBytes();

        byte[] inSize = FpCoder.intTo4Byte(nRecvDatLen);// 设置接收数据的长度
        System.arraycopy(inSize, 0, bytes, 8, 4);

        bytes[12] = (byte) DIRECTION_IN; // 接收数据
        bytes[16] = (byte) nCmdId; // 指令
        nRet = write(bytes);
        HexOut(TAG, bytes, 0, bytes.length);
        Logs.i(TAG, "nRet:" + nRet);

        return nRet;
    }

    public int doCommandIn(int nCmdId, int val) {
        int nRet = 0;
        byte[] bytes = getSendBytes();

        byte[] inSize = FpCoder.intTo4Byte(nRecvDatLen);// 设置接收数据的长度
        System.arraycopy(inSize, 0, bytes, 8, 4);

        bytes[12] = (byte) DIRECTION_IN; // 接收数据
        bytes[16] = (byte) nCmdId; // 指令
        bytes[17] = (byte) val;
        nRet = write(bytes);
        Logs.i(TAG, "nRet:" + nRet);

        return nRet;
    }

    public int doCommandIn(int nCmdId, int val, int offset, int index) {
        int nRet = 0;
        byte[] bytes = getSendBytes();

        byte[] inSize = FpCoder.intTo4Byte(nRecvDatLen);// 设置接收数据的长度
        System.arraycopy(inSize, 0, bytes, 8, 4);

        bytes[12] = (byte) DIRECTION_IN; // 接收数据
        bytes[16] = (byte) nCmdId; // 指令
        bytes[17] = (byte) val;
        bytes[18] = (byte) index;

        byte[] offsetSize = FpCoder.intTo4ByteBig(offset);
        if (mPid == 4865) {
            System.arraycopy(offsetSize, 0, bytes, 17, 4);
        } else {
            System.arraycopy(offsetSize, 0, bytes, 21, 4);
        }

        nRet = write(bytes);
        Logs.i(TAG, "nRet:" + nRet + " pid:" + mPid);

        // HexOut(TAG, bytes, 0, bytes.length);

        return nRet;
    }

    public int recvCmd() {
        int nRet = read(bRecvBuf, 0, nRecvDatLen);
        Logs.i(TAG, "read1 ret:" + nRet);
        if (nRet < 0) {
            return FpConst.TCY_NLNK;
        }
        nRecvDatLen = nRet;
        nRet = read(new byte[64], 0, 64);
        Logs.i(TAG, "read2 ret:" + nRet);
        return nRet;
    }

    /**
     * 用户友好提示
     *
     * @param val 0=短嘀，1=长嘀，2=嘀嘀
     * @return
     */
    private int spCmdHint(int val) {
        return doCommand(FpConst.CMD_HINTS, val, mWriteTimeout);
    }

    /**
     * 获取设备info
     *
     * @return
     */
    private int spCmdDevInfo() {
        int nRet;
        nRecvDatLen = 64;
        nRet = doCommandIn(FpConst.CMD_LNKOK);
        if (nRet < 0) {
            return FpConst.TCY_NLNK;
        }
        nRet = recvCmd();

        return nRet;
    }

    /**
     * 复位目标设备
     *
     * @return
     */
    private int spCmdDevReset() {
        return doCommand(FpConst.CMD_RESET);
    }

    /**
     * 获取sn命令
     *
     * @return
     */
    private int spCmdDevSn() {
        int nRet;
        nRecvDatLen = 64;
        nRet = doCommandIn(FpConst.CMD_GETSN);
        if (nRet < 0) {
            return FpConst.TCY_NLNK;
        }
        nRet = recvCmd();

        return nRet;
    }

    /**
     * 重新标定设备
     *
     * @return
     */
    private int spCmdBooTL() {
        return doCommand(FpConst.CMD_BOOTL, 0x01, 10000);
    }

    /**
     * 获取图片属性指令
     */
    private int spCmdDevFpHDR() {
        int nRet;
        nRecvDatLen = 64;
        nRet = doCommandIn(FpConst.CMD_FPHDR, FpConfig.getImageType());
        if (nRet < 0) {
            return FpConst.TCY_NLNK;
        }

        nRet = recvCmd();

        if (nRet < 0) {
            return FpConst.TCY_NLNK;
        }

        HexOut(TAG, bRecvBuf, 0, 28);

        // 解析图片属性
        bImgHead = new byte[28];
        byte[] sizeBytes = new byte[4];
        byte[] widthBytes = new byte[2];
        byte[] heightBytes = new byte[2];
        byte[] dpiBytes = new byte[2];
        System.arraycopy(bRecvBuf, 0, bImgHead, 0, 28);
        System.arraycopy(bRecvBuf, 6, widthBytes, 0, 2);
        System.arraycopy(bRecvBuf, 8, heightBytes, 0, 2);
        System.arraycopy(bRecvBuf, 10, dpiBytes, 0, 2);
        System.arraycopy(bRecvBuf, 20, sizeBytes, 0, 4);
        mImgSize = FpCoder.fourByteToInt(sizeBytes);
        mWidth = FpCoder.twoByteToInt(widthBytes);
        mHeight = FpCoder.twoByteToInt(heightBytes);
        mDPI = FpCoder.twoByteToInt(dpiBytes);
        Logs.i(TAG, "size:" + mImgSize + " width:" + mWidth + " height:"
                + mHeight + " dpi:" + mDPI);

        if (mImgSize < 0 || mImgSize > 100000 || mWidth < 0 || mHeight < 0) {
            nRet = FpConst.TCY_ERRO;
        }

        return nRet;
    }

    /**
     * 获取图像指令
     *
     * @return
     */
    private int spCmdDevImg(int limit) {
        // 获取图片属性
        int nRet = spCmdDevFpHDR();
        if (nRet < 0) {
            return nRet;
        }

        if (bImg == null) {
            bImg = new byte[mImgSize];
        }

        nRecvDatLen = MAX_USBFS_BUFFER_SIZE;
        int readSize = 0;

        // 发送获取指纹图像指令
        // nRet = doCommandIn(FpConst.CMD_RDIMG, FpConfig.getImageType());

        int offset = 0;
        int index = 0;
        int readImgSize = MAX_USBFS_BUFFER_SIZE;
        byte[] bTempImg = new byte[MAX_USBFS_BUFFER_SIZE];
        while ((nRet = doCommandIn(FpConst.CMD_RDIMG, FpConfig.getImageType(),
                offset, index)) > 0) {
            nRet = read(bTempImg, 0, readImgSize);

            if (nRet < 0) {
                break;
            }

            offset += nRet;

            int remain = (readSize + nRet) > mImgSize ? mImgSize - readSize
                    : nRet;

            Logs.d(TAG, "remain:" + remain + " offset:" + offset + " index:"
                    + index);
            try {

                System.arraycopy(bTempImg, 0, bImg, readSize, remain);

            } catch (ArrayIndexOutOfBoundsException e) {

                e.printStackTrace();
            }

            readSize += nRet;

            nRet = read(new byte[64], 0, 64);

            if (mImgSize - readSize < MAX_USBFS_BUFFER_SIZE) {
                readImgSize = mImgSize - readSize;
                nRecvDatLen = mImgSize - readSize;
            }

            if (readSize >= mImgSize) {
                break;
            }
        }

        if (nRet < 0) {
            return FpConst.TCY_NLNK;
        }

        // 读取指纹图像
        // byte[] bTempImg = new byte[MAX_USBFS_BUFFER_SIZE];
        // long start = System.currentTimeMillis();
        // while ((nRet = read(bTempImg, 0, MAX_USBFS_BUFFER_SIZE)) > 0) {
        // int remain = (readSize + nRet) > mImgSize ? mImgSize - readSize
        // : nRet;
        //
        // Logs.d(TAG, "remain:" + remain);
        //
        // System.arraycopy(bTempImg, 0, bImg, readSize, remain);
        // readSize += nRet;
        // if (readSize > mImgSize) {
        // break;
        // }
        // }

        if (nRet < 0) {
            return FpConst.TCY_NLNK;
        }
        nRecvDatLen = readSize;

        int[] imageAttr = new int[3];
        imageAttr[0] = mWidth; // width
        imageAttr[1] = mHeight; // height
        imageAttr[2] = mDPI; // resolution

        FpImage.setImageAttr(imageAttr);

        // byte[] imgAndHead = new byte[28 + bImg.length];
        // System.arraycopy(bImgHead, 0, imgAndHead, 0, 28);
        // System.arraycopy(bImg, 0, imgAndHead, 28, bImg.length);

        // int score = TCFP.TCFP_GetQualityScore(bImg, bImg.length, imageAttr);

        int score = 0;

        if (FPConfig.getFeatureType() < 5) {
            byte[] imgAndHead = new byte[28 + bImg.length];
            System.arraycopy(bImgHead, 0, imgAndHead, 0, 28);
            System.arraycopy(bImg, 0, imgAndHead, 28, bImg.length);

            score = TCFP.TCFP_GetQualityScore(imgAndHead, imgAndHead.length,
                    imageAttr);
        } else {
            score = TCFP.TCFP_GetQualityScore(bImg, bImg.length, imageAttr);
        }

        // int score = TCFP.TCFP_GetQualityScore(imgAndHead, imgAndHead.length,
        // imageAttr);
        Logs.i(TAG, "ImageSize:" + bImg.length + " score:" + score);

        if (FPConfig.getFeatureType() <= 6) {
            if (score < 60) {
                nRet = FpConst.TCY_NOFP; // GA score <= 40
            }
        } else if (FPConfig.getFeatureType() >= 7) {
            if (score >= limit) {
                nRet = FpConst.TCY_NOFP; // NFIQ >= 4
            }
        } else {
            nRet = FpConst.TCY_NOFP;
        }

        return nRet;
    }

    private int devGetImg(int nTimeout) {
        Logs.d(TAG, "devGetImg Enter.");
        isFuncTimeout(nTimeout);
        boolean isTimeout = false;

        int nRet = 0;

        while (!(isTimeout = isFuncTimeout())) {

            // 取消
            if (nCancelFlag == 1) {
                nCancelFlag = 0;
                return FpConst.TCY_CNCL;
            }
            // 交易超时
            if (isFuncTimeout() == true) {
                return FpConst.TCY_TMOT;
            }

            nRet = spCmdDevImg(4);
            if (nRet < 0) {
                if (nRet == FpConst.TCY_NOFP) {
                    continue;
                }
                isFuncTimeout(nTimeout);
                return FpConst.TCY_NLNK;
            } else {
                Logs.i(TAG, "devGetImg:" + nRet);
                break;
            }
        }

        if (isTimeout) {
            return FpConst.TCY_TMOT;
        }

        return nRet;
    }

    /**
     * read 方法必须确保可以读取到正确长度的数据，否则应该返回错误
     */
    private int readClear() {
        int nRet = FpConst.TCY_NLNK;
        return read(bRecvBuf, 0, 64);
    }

    private byte[] getSendBytes() {
        byte[] bytes = {0x55, 0x53, 0x42, 0x43, /*---4字节，固定---*/
                (byte) 0x30, 0x38, (byte) 0x8d, (byte) 0x8d, /*---4字节，随机数，我给写成固定格式了---*/
                0x00, 0x00, 0x00, 0x00, /*---4字节，返回数据的长度---*/
                0x00, /*--,1字节的方向控制，0x80为IN，0x00为out---*/
                0x00, 0x0a, /*---2字节的未知---*/
                (byte) 0xff, 0x00, 0x00, 0x00, /*---16字节的命令---*/
                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

        return bytes;
    }

    private void HexOut(String tag, byte[] bOut, int nOffSet, int nLen) {
        Logs.HexOut(tag, bOut, nOffSet, nLen);
    }

    /**
     * 获取设备info
     *
     * @return
     */
    public FingerResult getDevInfo() {
        Logs.i(TAG, "getDevInfo...");
        FingerResult result = new FingerResult(FpConst.TCY_SUCC);
        int nRet = 0;
        String info = "";
        nRet = spCmdDevInfo();
        if (nRet < 0) {
            return result.setStatus(FpConst.TCY_NLNK);
        }
        byte[] bInfo = new byte[nRecvDatLen];
        System.arraycopy(bRecvBuf, 0, bInfo, 0, nRecvDatLen);
        info = new String(bInfo);

        result.setStatus(nRet);
        result.setResult(info);

        return result;
    }

    /**
     * 获取设备sn
     *
     * @return
     */
    public FingerResult tcGetDevSn() {
        Logs.i(TAG, "tcGetDevSn...");
        FingerResult result = new FingerResult(FpConst.TCY_SUCC);
        int nRet = 0;

        String sSn = "";
        nRet = spCmdDevSn();
        if (nRet < 0) {
            return result.setStatus(FpConst.TCY_NLNK);
        }
        byte[] bSn = new byte[nRecvDatLen];
        HexOut(TAG, bRecvBuf, 0, nRecvDatLen);
        System.arraycopy(bRecvBuf, 0, bSn, 0, nRecvDatLen);
        sSn = new String(bSn);
        result.setStatus(nRet);
        result.setResult(sSn);
        return result;
    }

    /**
     * 标定设备
     *
     * @return
     */
    public FingerResult tcBooTL() {
        Logs.i(TAG, "tcBooTL");
        FingerResult result = new FingerResult(FpConst.TCY_SUCC);
        int nRet = spCmdBooTL();
        if (nRet < 0) {
            return result.setStatus(FpConst.TCY_NLNK);
        }

        return result;
    }

    /**
     * 获取指纹图像
     *
     * @return
     */
    public FingerResult tcGetImg(int nTimeout) {
        Logs.d(TAG, "tcGetImg Enter.");
        FingerResult result = new FingerResult();

        if (!isInitOk) {
            return result.setStatus(FpConst.DOG_NONE);
        }

        int nRet = 0;

        nRet = devGetImg(nTimeout);
        if (nRet < 0) {
            return result.setStatus(nRet);
        }

        Logs.i(TAG, "tcGetImg:" + nRet);
        FpImage fpImage = new FpImage();
        fpImage.saveRawAsBmpBuf(bImg, mWidth, mHeight);
        result.setRawBytes(bImg);
        result.setImgBytes(fpImage.getBmpImgBuf());
        result.setStatus(nRet);

        nRet = spCmdHint(0);
        Logs.d(TAG, "spCmdHint:" + nRet);

        return result;
    }

    /**
     * 获取指纹特征
     *
     * @param nTimeout
     * @return
     */
    public FingerResult tcGetFeature(int nTimeout) {
        Logs.d(TAG, "tcGetFeature Enter.");
        FingerResult result = new FingerResult();

        if (!isInitOk) {
            return result.setStatus(FpConst.DOG_NONE);
        }

        int nRet = devGetImg(nTimeout);

        if (nRet < 0) {
            return result.setStatus(nRet);
        }

        result.setStatus(nRet);
        int[] imageAttr = new int[3];
        imageAttr[0] = mWidth; // width
        imageAttr[1] = mHeight; // height
        imageAttr[2] = mDPI; // resolution

        byte[] bFeature = new byte[512];

        if (FPConfig.getFeatureType() < 5) {
            byte[] imgAndHead = new byte[28 + bImg.length];
            System.arraycopy(bImgHead, 0, imgAndHead, 0, 28);
            System.arraycopy(bImg, 0, imgAndHead, 28, bImg.length);

            nRet = TCFP.TCFP_FeatureExtract(bFeature, imgAndHead,
                    imgAndHead.length, imageAttr);
        } else {
            nRet = TCFP.TCFP_FeatureExtract(bFeature, bImg, bImg.length,
                    imageAttr);
        }

        Logs.i(TAG, "feature length:" + nRet);

        if (nRet < 0) {
            return result.setStatus(nRet);
        }
        result.setTemplate(bFeature);
        FpImage fpImage = new FpImage();
        result.setRawBytes(bImg);
        fpImage.saveRawAsBmpBuf(bImg, mWidth, mHeight);
        result.setImgBytes(fpImage.getBmpImgBuf());

        return result;
    }

    /**
     * 注册指纹
     *
     * @param timeout
     * @param msgCallback
     * @return
     */
    public FingerResult tcEnroll(int timeout, MsgCallback msgCallback) {
        mCallback = msgCallback;
        long start = System.currentTimeMillis();
        FingerResult result = null;
        byte[] feature1 = null;
        byte[] feature2 = null;
        byte[] feature3 = null;

        int nRet = FpConst.TCY_NOFP;
        int i = 0;
        while (true) {
            if (System.currentTimeMillis() - start > timeout) {
                return new FingerResult(FpConst.TCY_TMOT);
            }
            if (isCancel()) {
                nCancelFlag = 0;
                return new FingerResult(FpConst.TCY_CNCL);
            }

            if (nRet != FpConst.TCY_NOFP) {
                sendMsg(FpConst.TCY_TOLV, null);
                nRet = spCmdDevImg(4);
                continue;
            }

            sendMsg(FpConst.TCY_CAPTUREIMAGE, null);

            result = tcGetFeature(3000);

            if (result.getStatus() >= 0) {
                spCmdHint(0);
                if (i == 0) {
                    feature1 = result.getTemplate();
                } else if (i == 1) {
                    feature2 = result.getTemplate();
                } else if (i == 2) {
                    feature3 = result.getTemplate();
                }
                i++;
                sendMsg(FpConst.TCY_GETIMAGE, result.getImgBytes());
                nRet = spCmdDevImg(4);
            } else if (result.getStatus() == FpConst.TCY_CNCL) {
                return new FingerResult(FpConst.TCY_CNCL);
            } else if (result.getStatus() == FpConst.TCY_NLNK) {
                Logs.e(TAG, "nRet:" + result.getStatus());
                return new FingerResult(FpConst.TCY_NLNK);
            }

            if (i > 2) {
                byte[] template = new byte[512];

                int code = TCFP.TCFP_FeatureTempletExtract(feature1, feature2,
                        feature3, template);
                Logs.i(TAG, "合成模板：" + code);

                if (code < 0) {
                    return new FingerResult(FpConst.TCY_NSAM);
                }
                result.setTemplate(template);

                return result;
            }
        }
    }

    private void sendMsg(int code, byte[] img) {

        if (mCallback != null) {
            mCallback.call(code, img);
        }
    }

    private void saveTemplateToDisk(byte[] bytes, String fileName) {
        if (bytes == null) {
            return;
        }
        File file = new File(Environment.getExternalStorageDirectory()
                .getPath() + "/" + fileName);
        // 创建FileOutputStream对象
        FileOutputStream outputStream = null;
        // 创建BufferedOutputStream对象
        BufferedOutputStream bufferedOutputStream = null;
        try {
            // 如果文件存在则删除
            if (file.exists()) {
                file.delete();
            }
            // 在文件系统中根据路径创建一个新的空文件
            file.createNewFile();
            // 获取FileOutputStream对象
            outputStream = new FileOutputStream(file);
            // 获取BufferedOutputStream对象
            bufferedOutputStream = new BufferedOutputStream(outputStream);
            // 往文件所在的缓冲输出流中写byte数据
            bufferedOutputStream.write(bytes);
            // 刷出缓冲输出流，该步很关键，要是不执行flush()方法，那么文件的内容是空的。
            bufferedOutputStream.flush();

            Logs.i(TAG, "保存文件成功");
        } catch (Exception e) {
            // 打印异常信息
            e.printStackTrace();
        } finally {
            // 关闭创建的流对象
            if (outputStream != null) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (bufferedOutputStream != null) {
                try {
                    bufferedOutputStream.close();
                } catch (Exception e2) {
                    e2.printStackTrace();
                }
            }
        }
    }
}
